﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media.Imaging;
using System.Drawing;
using WebViewerTest.NativeMethods;
using System.Windows.Forms;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Windows.Forms.Integration;
using System.Runtime.InteropServices.ComTypes;
using System.Threading;

namespace WebViewerTest
{
	public class WebBrowserProxy : Grid, IAdviseSink, IDisposable
	{
		public static DependencyProperty BrowserProperty = DependencyProperty.Register(
			"Browser",
			typeof( WinFormsWebBrowser ),
			typeof( WebBrowserProxy ),
			new FrameworkPropertyMetadata(
				null,
				FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsRender,
				OnBrowserChanged ) );

		private object bitmapLock = new object();
		private Bitmap bitmap;
		private WriteableBitmap writeableBitmap;
		private Uri url;

		private IViewObject view;
		private System.Windows.Point lastMouse;

		private Thread thread = null;
		private AutoResetEvent takeSnapshot = new AutoResetEvent( false );
		private AutoResetEvent stop = new AutoResetEvent( false );
		private ManualResetEvent stopped = new ManualResetEvent( false );

		public WebBrowserProxy()
		{
		}

		private void Start()
		{
			stop.Reset();
			stopped.Reset();

			( thread = new Thread( Run ) ).Start();
		}

		public void Stop()
		{
			stop.Set();
			stopped.WaitOne();
			thread = null;

			if ( view != null )
			{
				view.SetAdvise( 1, 0, null );
				view = null;
			}
		}

		private void Run()
		{
			var handles = new WaitHandle[] { stop, takeSnapshot };

			while ( WaitHandle.WaitAny( handles ) != 0 /* stop */ )
			{
				TakeSnapshot();
			}

			stopped.Set();
		}

		private void TakeSnapshot()
		{
			lock ( bitmapLock )
			{
				if ( view != null && bitmap != null )
				{
					var bounds = new COMRECT( 0, 0, bitmap.Width, bitmap.Height );

					using ( var graphics = Graphics.FromImage( bitmap ) )
					{
						var hdc = graphics.GetHdc();
						view.Draw( 1, -1, (IntPtr)0, null, (IntPtr)0, (IntPtr)hdc, bounds, bounds, (IntPtr)0, 0 );
						graphics.ReleaseHdc( hdc );
					}

					var data = bitmap.LockBits(
						new System.Drawing.Rectangle( 0, 0, bitmap.Width, bitmap.Height ),
						System.Drawing.Imaging.ImageLockMode.ReadOnly,
						bitmap.PixelFormat );

					Dispatcher.Invoke( (Action)delegate
					{
						writeableBitmap.WritePixels(
							new Int32Rect( 0, 0, bitmap.Width, bitmap.Height ),
							data.Scan0,
							data.Height * data.Stride,
							data.Stride );
					} );

					bitmap.UnlockBits( data );
				}
			}
		}

		public WinFormsWebBrowser Browser
		{
			get { return (WinFormsWebBrowser)GetValue( BrowserProperty ); }
			set { SetValue( BrowserProperty, value ); }
		}

		public Uri Source
		{
			get { return url; }
			set { this.url = value; Navigate(); }
		}

		protected override void OnMouseMove( System.Windows.Input.MouseEventArgs e )
		{
			var local = e.GetPosition( this );
			var global = PointToScreen( local );
			var browser = Browser;
			lastMouse = local;
			browser.SendMouseMove( global, local );
			e.Handled = false;
		}

		protected override void OnMouseDown( MouseButtonEventArgs e )
		{
			var p = e.GetPosition( this );
			var browser = Browser;
			e.Handled = false;
		}

		protected override void OnMouseUp( MouseButtonEventArgs e )
		{
			var p = e.GetPosition( this );
			var browser = Browser;
			browser.SendMouseDown( (int)p.X, (int)p.Y );
			browser.SendMouseUp( (int)p.X, (int)p.Y );
			e.Handled = false;
		}

		private void Navigate()
		{
			try
			{
				var browser = Browser;

				if ( browser != null ) browser.Url = url;
			}
			catch
			{
				// TODO: Error handling...
			}
		}

		private static void OnBrowserChanged( DependencyObject obj, DependencyPropertyChangedEventArgs args )
		{
			var proxy = obj as WebBrowserProxy;
			var oldBrowser = args.OldValue as WinFormsWebBrowser;
			var newBrowser = args.NewValue as WinFormsWebBrowser;

			if ( oldBrowser != newBrowser )
			{
				if ( oldBrowser != null )
				{
					proxy.Stop();
					proxy.RemoveHandlers( oldBrowser );
				}

				if ( newBrowser != null )
				{
					proxy.Start();
					proxy.AttachHandlers( newBrowser );
					proxy.Navigate();
				}
			}
		}

		private void RemoveHandlers( WinFormsWebBrowser browser )
		{
			browser.ViewChanged -= browser_Navigated;
//			browser.ProgressChanged -= browser_ProgressChanged;
//			browser.StatusTextChanged -= browser_StatusTextChanged;
//			browser.DocumentCompleted -= browser_DocumentCompleted;
//			browser.Changed -= browser_Changed;
//			browser.Invalidated -= browser_Invalidated;*/
		}

		private void AttachHandlers( WinFormsWebBrowser browser )
		{
			browser.ViewChanged += browser_Navigated;
//			browser.ProgressChanged += browser_ProgressChanged;
//			browser.StatusTextChanged += browser_StatusTextChanged;
//			browser.DocumentCompleted += browser_DocumentCompleted;
//			browser.Changed += browser_Changed;
//			browser.Invalidated += browser_Invalidated;*/

//			view = browser.ActiveXInstance as IViewObject;
		}

		void browser_Navigated( object sender, EventArgs e )
		{
//			if ( browser.Document != null ) browser.Document.Window.Error += delegate { };
		}
/*
		void browser_ProgressChanged( object sender, WebBrowserProgressChangedEventArgs e )
		{
			takeSnapshot.Set();
//			Browser.Invalidate();
		}

		void browser_DocumentCompleted( object sender, WebBrowserDocumentCompletedEventArgs e )
		{
			takeSnapshot.Set();
//			Browser.Invalidate();
		}
/*
		void browser_StatusTextChanged( object sender, EventArgs e )
		{
//			Browser.Invalidate();
		}

		void browser_Changed( object sender, EventArgs e )
		{
//			Browser.Invalidate();
		}

		void browser_Invalidated( object sender, InvalidateEventArgs e )
		{
//			invalidRect = Rectangle.Union( invalidRect, e.InvalidRect );
//			InvalidateVisual();
//			Snapshot( e.InvalidRect );
		}
*/
		protected override System.Windows.Size MeasureOverride( System.Windows.Size constraint )
		{
			return constraint;
		}

		protected override System.Windows.Size ArrangeOverride( System.Windows.Size arrangeSize )
		{
			lock ( bitmapLock )
			{
				if ( bitmap == null || ( bitmap.Width != (int)DesiredSize.Width || bitmap.Height != (int)DesiredSize.Height ) )
				{
					// if ( writeableBitmap != null ) writeableBitmap.Changed -= OnChanged;
					if ( bitmap != null ) bitmap.Dispose();
					bitmap = new Bitmap( (int)DesiredSize.Width, (int)DesiredSize.Height );
					writeableBitmap = new WriteableBitmap( bitmap.Width, bitmap.Height, 96, 96, PixelFormats.Pbgra32, null );
					// writeableBitmap.Changed += OnChanged;
				}
			}

			var browser = Browser;

			if ( browser != null && ( browser.Width != bitmap.Width || browser.Height != bitmap.Height ) )
			{
				browser.Width = bitmap.Width;
				browser.Height = bitmap.Height;
			}

			return arrangeSize;
		}

		protected override void OnRender( DrawingContext drawingContext )
		{
			if ( writeableBitmap != null )
			{
				drawingContext.DrawImage( writeableBitmap, new Rect( new System.Windows.Point(), base.RenderSize ) );
			}
		}

		protected override void OnGotFocus( RoutedEventArgs e )
		{
			base.OnGotFocus( e );

			var browser = Browser;

			if ( browser != null ) browser.Focus();
		}

		#region IAdviseSink Members

		void IAdviseSink.OnClose()
		{
		}


		void IAdviseSink.OnDataChange( ref FORMATETC format, ref STGMEDIUM stgmedium )
		{
		}

		void IAdviseSink.OnRename( IMoniker moniker )
		{
		}

		void IAdviseSink.OnSave()
		{
		}

		void IAdviseSink.OnViewChange( int aspect, int index )
		{
			takeSnapshot.Set();
		}

		#endregion

		#region IDisposable Members

		public void Dispose()
		{
			Stop();
		}

		#endregion
	}
}